package com.landiao.common.weixin;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.landiao.common.utils.HttpFileDownload;
import com.landiao.common.utils.RandomUtils;
import com.landiao.common.utils.SHA1;
import com.landiao.common.utils.WeixinUtils;
import com.landiao.common.utils.WeixinUtils.RequestMethodEnum;
import com.landiao.common.utils.WxCryptUtil;
import com.landiao.common.weixin.bean.WxAccessToken;
import com.landiao.common.weixin.bean.WxError;
import com.landiao.common.weixin.bean.WxJsapiSignature;
import com.landiao.common.weixin.bean.WxMenu;
import com.landiao.common.weixin.bean.WxMpCustomMessage;
import com.landiao.common.weixin.bean.WxMpMassGroupMessage;
import com.landiao.common.weixin.bean.WxMpMassNews;
import com.landiao.common.weixin.bean.WxMpMassOpenIdsMessage;
import com.landiao.common.weixin.bean.WxMpMassPreviewMessage;
import com.landiao.common.weixin.bean.WxMpMassSendResult;
import com.landiao.common.weixin.bean.WxMpMassUploadResult;
import com.landiao.common.weixin.bean.WxMpMaterialUploadResult;
import com.landiao.common.weixin.bean.WxMpOAuth2AccessToken;
import com.landiao.common.weixin.bean.WxMpPayOrderQuery;
import com.landiao.common.weixin.bean.WxMpPrepayIdResult;
import com.landiao.common.weixin.bean.WxMpTemplateMessage;
import com.landiao.common.weixin.bean.WxMpUser;
import com.landiao.common.weixin.bean.WxMpUserList;

public class WxMpService{

  protected final Logger log = LoggerFactory.getLogger(WxMpService.class);

  /**
   * 全局的是否正在刷新access token的锁
   */
  protected final Object globalAccessTokenRefreshLock = new Object();

  /**
   * 全局的是否正在刷新jsapi_ticket的锁
   */
  protected final Object globalJsapiTicketRefreshLock = new Object();

  protected WxMpConfigStorage wxMpConfigStorage;


  private int retrySleepMillis = 1000;

  private int maxRetryTimes = 3;

  /**
   * <pre>
   * 验证推送过来的消息的正确性
   * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=验证消息真实性
   * </pre>
   * @param timestamp
   * @param nonce
   * @param signature
   * @return
   */
  public boolean checkSignature(String timestamp, String nonce, String signature) {
    try {
      return SHA1.gen(wxMpConfigStorage.getToken(), timestamp, nonce).equals(signature);
    } catch (Exception e) {
      return false;
    }
  }

  /**
   * 获取access_token, 不强制刷新access_token
   * @see #getAccessToken(boolean)
   * @return
   * @throws WxErrorException
   */
  public String getAccessToken() throws WxErrorException {
    return getAccessToken(false);
  }

  /**
   * <pre>
   * 获取access_token，本方法线程安全
   * 且在多线程同时刷新时只刷新一次，避免超出2000次/日的调用次数上限
   * 
   * 另：本service的所有方法都会在access_token过期是调用此方法
   * 
   * 程序员在非必要情况下尽量不要主动调用此方法

   * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=获取access_token
   * </pre>
   * @param forceRefresh 强制刷新
   * @return
   * @throws me.chanjar.weixin.common.exception.WxErrorException
   */
  public String getAccessToken(boolean forceRefresh) throws WxErrorException {
	  if (forceRefresh) {
		  wxMpConfigStorage.expireAccessToken();
	  }
	  if (wxMpConfigStorage.isAccessTokenExpired()) {
		  synchronized (globalAccessTokenRefreshLock) {
			  if (wxMpConfigStorage.isAccessTokenExpired()) {
				  String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential"
						  + "&appid=" + wxMpConfigStorage.getAppId()
						  + "&secret=" + wxMpConfigStorage.getSecret()
						  ;
				  String resultContent = WeixinUtils.httpRequest(url, RequestMethodEnum.GET, null);
				  WxError error = WxError.fromJson(resultContent);
				  if (error.getErrcode() != 0) {
					  throw new WxErrorException(error);
				  }
				  WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
				  wxMpConfigStorage.updateAccessToken(accessToken.getAccess_token(), accessToken.getExpires_in());
			  }
		  }
	  }
	  return wxMpConfigStorage.getAccessToken();
  }

  public String getJsapiTicket() throws WxErrorException {
	  return getJsapiTicket(false);
  }

  public String getJsapiTicket(boolean forceRefresh) throws WxErrorException {
	  if (forceRefresh) {
		  wxMpConfigStorage.expireJsapiTicket();
	  }
	  if (wxMpConfigStorage.isJsapiTicketExpired()) {
		  synchronized (globalJsapiTicketRefreshLock) {
			  if (wxMpConfigStorage.isJsapiTicketExpired()) {
				  String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi";
				  String resultContent = execute(url, RequestMethodEnum.GET, null);
				  JSONObject jsonObject =  JSONObject.parseObject(resultContent);
				  String jsapiTicket = jsonObject.getString("ticket");
				  int expiresInSeconds = jsonObject.getIntValue("expires_in");
				  wxMpConfigStorage.updateJsapiTicket(jsapiTicket, expiresInSeconds);
			  }
		  }
	  }
	  return wxMpConfigStorage.getJsapiTicket();
  }

  public WxJsapiSignature createJsapiSignature(String url) throws WxErrorException {
	  long timestamp = System.currentTimeMillis() / 1000;
	  String noncestr = RandomUtils.getRandomStr();
	  String jsapiTicket = getJsapiTicket(false);
	  try {
		  String signature = SHA1.genWithAmple(
				  "jsapi_ticket=" + jsapiTicket,
				  "noncestr=" + noncestr,
				  "timestamp=" + timestamp,
				  "url=" + url
				  );
		  WxJsapiSignature jsapiSignature = new WxJsapiSignature();
		  jsapiSignature.setTimestamp(timestamp);
		  jsapiSignature.setNoncestr(noncestr);
		  jsapiSignature.setUrl(url);
		  jsapiSignature.setSignature(signature);
		  return jsapiSignature;
	  } catch (NoSuchAlgorithmException e) {
		  throw new RuntimeException(e);
	  }
  }
	  
  /**
   * <pre>
   * 自定义菜单创建接口
   * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单创建接口
   * </pre>
   * @param menu
   * @throws WxErrorException
   */
  public void menuCreate(WxMenu menu) throws WxErrorException {
    String url = "https://api.weixin.qq.com/cgi-bin/menu/create";
    execute(url, RequestMethodEnum.POST, menu.toJson());
  }
  
  /**
   * <pre>
   * 自定义菜单删除接口
   * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单删除接口
   * </pre>
   * @throws WxErrorException
   */
  public void menuDelete() throws WxErrorException {
    String url = "https://api.weixin.qq.com/cgi-bin/menu/delete";
    execute(url, RequestMethodEnum.GET,  null);
  }

  /**
   * <pre>
   * 获取用户基本信息
   * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=获取用户基本信息
   * </pre>
   * @param openid  用户openid
   * @param lang    语言，zh_CN 简体(默认)，zh_TW 繁体，en 英语 
   * @return
   * @throws WxErrorException
   */
  public WxMpUser userInfo(String openid, String lang) throws WxErrorException {
    String url = "https://api.weixin.qq.com/cgi-bin/user/info";
    lang = lang == null ? "zh_CN" : lang;
    String responseContent = execute(url, RequestMethodEnum.GET,  "openid=" + openid + "&lang=" + lang);
    return WxMpUser.fromJson(responseContent);
  }
  
  /**
   * <pre>
   * 获取关注者列表
   * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=获取关注者列表
   * </pre>
   * @param next_openid  可选，第一个拉取的OPENID，null为从头开始拉取
   * @return
   * @throws WxErrorException
   */
  public WxMpUserList userList(String next_openid) throws WxErrorException {
    String url = "https://api.weixin.qq.com/cgi-bin/user/get";
    String responseContent = execute(url, RequestMethodEnum.GET, next_openid == null ? null : "next_openid=" + next_openid);
    return WxMpUserList.fromJson(responseContent);
  }
  
  /**
   * <pre>
   * 发送模板消息
   * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=模板消息接口
   * </pre>
   * @param templateMessage
   * @throws WxErrorException
   * @return msgid
   */
  public String templateSend(WxMpTemplateMessage templateMessage) throws WxErrorException {
    String url = "https://api.weixin.qq.com/cgi-bin/message/template/send";
    String json = templateMessage.toJson();
//    log.debug(json);
    String responseContent = execute(url, RequestMethodEnum.POST,json);
    JSONObject jsonObject =  JSONObject.parseObject(responseContent);
    return jsonObject.getString("msgid");
  }

  /**
   * 设置所属行业
   */
  public void templateSetIndustry(String industry_id1,String industry_id2) throws WxErrorException {
    String url = "https://api.weixin.qq.com/cgi-bin/template/api_set_industry";
    Map<String, String> map = new HashMap<>();
    map.put("industry_id1", industry_id1);//1
    map.put("industry_id2", industry_id2);//23
    execute(url, RequestMethodEnum.POST,JSON.toJSONString(map));
  }
  
  /**
   * 获得模板ID
   */
  public String templateAdd(String template_id_short) throws WxErrorException {
    String url = "https://api.weixin.qq.com/cgi-bin/template/api_add_template";
    Map<String, String> map = new HashMap<>();
    map.put("template_id_short", template_id_short);
    String responseContent = execute(url, RequestMethodEnum.POST,JSON.toJSONString(map));
    JSONObject jsonObject =  JSONObject.parseObject(responseContent);
    return jsonObject.getString("template_id");
  }
  
  /**
   * <pre>
   * 构造oauth2授权的url连接
   * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=网页授权获取用户基本信息
   * </pre>
   * @param redirectURI
   *   用户授权完成后的重定向链接，无需urlencode, 方法内会进行encode
   * @param scope
   * @param state
   * @return code
   */
  public String oauth2buildAuthorizationUrl(String redirectURI, String scope, String state) {
    String url = "https://open.weixin.qq.com/connect/oauth2/authorize?" ;
    url += "appid=" + wxMpConfigStorage.getAppId();
    try {
		url += "&redirect_uri=" + URLEncoder.encode(redirectURI, "UTF-8");
	} catch (UnsupportedEncodingException e) {
		e.printStackTrace();
	}
    url += "&response_type=code";
    url += "&scope=" + scope;
    if (state != null) {
      url += "&state=" + state;
    }
    url += "#wechat_redirect";
    return url;
  }

  /**
   * <pre>
   * 用code换取oauth2的access token
   * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=网页授权获取用户基本信息
   * </pre>
   * @param code
   * @return
   */
  public WxMpOAuth2AccessToken oauth2getAccessToken(String code) throws WxErrorException {
	  String url = "https://api.weixin.qq.com/sns/oauth2/access_token?";
	  url += "appid=" +  wxMpConfigStorage.getAppId();
	  url += "&secret=" + wxMpConfigStorage.getSecret();
	  url += "&code=" + code;
	  url += "&grant_type=authorization_code";

	  try {
		  String responseText = WeixinUtils.httpRequest(url, RequestMethodEnum.GET, null);
		  return WxMpOAuth2AccessToken.fromJson(responseText);
	  } catch (Exception e) {
		  throw new RuntimeException(e);
	  }
  }

  /**
   * <pre>
   * 刷新oauth2的access token
   * </pre>
   * @param refreshToken
   * @return
   */
  public WxMpOAuth2AccessToken oauth2refreshAccessToken(String refreshToken) throws WxErrorException {
    String url = "https://api.weixin.qq.com/sns/oauth2/refresh_token?";
    url += "appid=" + wxMpConfigStorage.getAppId();
    url += "&grant_type=refresh_token";
    url += "&refresh_token=" + refreshToken;

    try {
      String responseText = WeixinUtils.httpRequest(url, RequestMethodEnum.GET, null);
      return WxMpOAuth2AccessToken.fromJson(responseText);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * 当本Service没有实现某个API的时候，可以用这个，针对所有微信API中的GET请求
   * @param url
   * @param queryParam
   * @return
   * @throws WxErrorException
   */
  public String get(String url, String queryParam) throws WxErrorException {
	  if (url.indexOf('?') == -1) {
		  url += '?';
	  }
	  url += url.endsWith("?") ? queryParam : '&' + queryParam;
	  return execute(url,RequestMethodEnum.GET, null);
  }

  /**
   * 当本Service没有实现某个API的时候，可以用这个，针对所有微信API中的POST请求
   * @param url
   * @param postData
   * @return
   * @throws WxErrorException
   */
  public String post(String url, String postData) throws WxErrorException {
    return execute(url, RequestMethodEnum.POST,postData);
  }

  /**
   * 新增其他类型永久素材
   * @param mediaType
   * @param file
   * @return
   * @throws WxErrorException
   */
  public WxMpMaterialUploadResult materialFileUpload(String mediaType, File file) throws WxErrorException {
	  String url = "https://api.weixin.qq.com/cgi-bin/material/add_material?type=" + mediaType;
	  String responseContent = executeInternal(url, file);
	  log.debug("materialFileUpload:{}",responseContent);
	  return WxMpMaterialUploadResult.fromJson(responseContent);
  }
  
  /**
   * <pre>
   * 上传群发用的图文消息，上传后才能群发图文消息 
   * 
   * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=高级群发接口
   * </pre>
   * @param news
   * @throws WxErrorException
   * @see #massGroupMessageSend(me.chanjar.weixin.mp.bean.WxMpMassGroupMessage)
   * @see #massOpenIdsMessageSend(me.chanjar.weixin.mp.bean.WxMpMassOpenIdsMessage)
   */
  public WxMpMassUploadResult massNewsUpload(WxMpMassNews news) throws WxErrorException {
//	  String url = "https://api.weixin.qq.com/cgi-bin/media/uploadnews";
	  String url = "https://api.weixin.qq.com/cgi-bin/material/add_news";
	  String responseContent = execute(url, RequestMethodEnum.POST,news.toJson());
	  return WxMpMassUploadResult.fromJson(responseContent);
  }

  /**
   * <pre>
   * 分组群发消息
   * 如果发送图文消息，必须先使用 {@link #massNewsUpload(me.chanjar.weixin.mp.bean.WxMpMassNews)} 获得media_id，然后再发送
   * 如果发送视频消息，必须先使用 {@link #massVideoUpload(me.chanjar.weixin.mp.bean.WxMpMassVideo)} 获得media_id，然后再发送
   * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=高级群发接口
   * </pre>
   * @param message
   * @throws WxErrorException
   * @return 
   */
  public WxMpMassSendResult massGroupMessageSend(WxMpMassGroupMessage message) throws WxErrorException {
	  String url = "https://api.weixin.qq.com/cgi-bin/message/mass/sendall";
	  String responseContent = execute(url, RequestMethodEnum.POST,message.toJson());
	  return WxMpMassSendResult.fromJson(responseContent);
  }

  /**
   * <pre>
   * 按openId列表群发消息
   * 如果发送图文消息，必须先使用 {@link #massNewsUpload(me.chanjar.weixin.mp.bean.WxMpMassNews)} 获得media_id，然后再发送
   * 如果发送视频消息，必须先使用 {@link #massVideoUpload(me.chanjar.weixin.mp.bean.WxMpMassVideo)} 获得media_id，然后再发送
   * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=高级群发接口
   * </pre>
   * @param message
   * @return
   * @throws WxErrorException
   */
  public WxMpMassSendResult massOpenIdsMessageSend(WxMpMassOpenIdsMessage message) throws WxErrorException {
	  String url = "https://api.weixin.qq.com/cgi-bin/message/mass/send";
	  String responseContent = execute(url, RequestMethodEnum.POST,message.toJson());
	  return WxMpMassSendResult.fromJson(responseContent);
  }
  
  /**
   * <pre>
   * 预览群发消息
   * 如果发送图文消息，必须先使用 {@link #massNewsUpload(me.chanjar.weixin.mp.bean.WxMpMassNews)} 获得media_id，然后再发送
   * 如果发送视频消息，必须先使用 {@link #massVideoUpload(me.chanjar.weixin.mp.bean.WxMpMassVideo)} 获得media_id，然后再发送
   * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=高级群发接口
   * </pre>
   * @param message
   * @return
   * @throws WxErrorException
   */
  public WxMpMassSendResult massPreviewMessageSend(WxMpMassPreviewMessage message) throws WxErrorException {
	  String url = "https://api.weixin.qq.com/cgi-bin/message/mass/preview";
	  String responseContent = execute(url, RequestMethodEnum.POST,message.toJson());
	  return WxMpMassSendResult.fromJson(responseContent);
  }
  
  /**
   * <pre>
   * 发送客服消息
   * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=发送客服消息
   * </pre>
   * @param message
   * @throws WxErrorException
   */
  public void customMessageSend(WxMpCustomMessage message) throws WxErrorException {
	  String url = "https://api.weixin.qq.com/cgi-bin/message/custom/send";
	  execute(url, RequestMethodEnum.POST,message.toJson());
  }
  
  public boolean mediaDownload(String media_id, String localFilePath) throws WxErrorException {
	  String accessToken = getAccessToken(false);
//	  String url = "http://file.api.weixin.qq.com/cgi-bin/media/get?access_token=" + accessToken+"&media_id=" + media_id;
	  String url = "https://api.weixin.qq.com/cgi-bin/media/get?access_token=" + accessToken+"&media_id=" + media_id;
	  String resultContent = HttpFileDownload.httpDownload(url, localFilePath);
	  if(StringUtils.isNotEmpty(resultContent)){
		  log.error("微信下载图片失败，{}", resultContent);
		  WxError error = WxError.fromJson(resultContent);
		  if (error.getErrcode() != 0) {
			  if (error.getErrcode() == 42001) {
				  // 强制设置wxMpConfigStorage它的access token过期了，这样在下一次请求里就会刷新access token
				  wxMpConfigStorage.expireAccessToken();
				  return mediaDownload(media_id,localFilePath);
			  }else{
				  return false;
			  }
		  }
	  }
	  return true;
  }
  
  /**
   * 向微信端发送请求，在这里执行的策略是当发生access_token过期时才去刷新，然后重新执行请求，而不是全局定时请求
   * @param executor
   * @param uri
   * @param data
   * @return
   * @throws WxErrorException
   */
  public String execute(String uri,RequestMethodEnum method, String data) throws WxErrorException {
	  int retryTimes = 0;
	  do {
		  try {
			  return executeInternal(uri,method, data);
		  } catch (WxErrorException e) {
			  WxError error = e.getError();
			  /**
			   * -1 系统繁忙, 1000ms后重试
			   */
			  if (error.getErrcode() == -1) {
				  int sleepMillis = retrySleepMillis * (1 << retryTimes);
				  try {
					  log.debug("微信系统繁忙，{}ms 后重试(第{}次)", sleepMillis, retryTimes + 1);
					  Thread.sleep(sleepMillis);
				  } catch (InterruptedException e1) {
					  throw new RuntimeException(e1);
				  }
			  } else {
				  throw e;
			  }
		  }
	  } while(++retryTimes < maxRetryTimes);

	  throw new RuntimeException("微信服务端异常，超出重试次数");
  }

  protected String executeInternal(String uri,File file) throws WxErrorException {
	  String accessToken = getAccessToken(false);

	  String uriWithAccessToken = uri;
	  uriWithAccessToken += uri.indexOf('?') == -1 ? "?access_token=" + accessToken : "&access_token=" + accessToken;

	  String resultContent = WeixinUtils.sendFile(uriWithAccessToken, file);
	  WxError error = WxError.fromJson(resultContent);
	  if (error.getErrcode() != 0) {
		  /*
		   * 发生以下情况时尝试刷新access_token
		   * 40001 获取access_token时AppSecret错误，或者access_token无效
		   * 42001 access_token超时
		   */
		  if (error.getErrcode() == 42001) {
			  // 强制设置wxMpConfigStorage它的access token过期了，这样在下一次请求里就会刷新access token
			  wxMpConfigStorage.expireAccessToken();
			  return executeInternal(uri,file);
		  }else{
			  throw new WxErrorException(error);
		  }
	  }
	  return resultContent;
  }
  
  protected String executeInternal(String uri,RequestMethodEnum method, String data) throws WxErrorException {
	  String accessToken = getAccessToken(false);

	  String uriWithAccessToken = uri;
	  uriWithAccessToken += uri.indexOf('?') == -1 ? "?access_token=" + accessToken : "&access_token=" + accessToken;
	  if (method.equals(RequestMethodEnum.GET)&&data != null) {
		  if (uriWithAccessToken.indexOf('?') == -1) {
			  uriWithAccessToken += '?';
		  }
		  uriWithAccessToken += uriWithAccessToken.endsWith("?") ? data : '&' + data;
	  }
	  String resultContent = WeixinUtils.httpRequest(uriWithAccessToken, method, data);
	  log.debug("wx:{}",resultContent);
	  WxError error = WxError.fromJson(resultContent);
	  if (error.getErrcode() != 0) {
		  /*
		   * 发生以下情况时尝试刷新access_token
		   * 40001 获取access_token时AppSecret错误，或者access_token无效
		   * 42001 access_token超时
		   */
		  if (error.getErrcode() == 42001 || error.getErrcode() == 40001) {
			  // 强制设置wxMpConfigStorage它的access token过期了，这样在下一次请求里就会刷新access token
			  wxMpConfigStorage.expireAccessToken();
			  return execute(uri,method, data);
		  }else{
			  throw new WxErrorException(error);
		  }
	  }
	  return resultContent;
  }

  /**
   * 注入 {@link WxMpConfigStorage} 的实现
   * @param wxConfigProvider
   */
  public void setWxMpConfigStorage(WxMpConfigStorage wxConfigProvider) {
    this.wxMpConfigStorage = wxConfigProvider;
  }

  public WxMpConfigStorage getWxMpConfigStorage() {
	  return wxMpConfigStorage;
  }

/**
   * <pre>
   * 设置当微信系统响应系统繁忙时，要等待多少 retrySleepMillis(ms) * 2^(重试次数 - 1) 再发起重试
   * 默认：1000ms
   * </pre>
   * @param retrySleepMillis
   */
  public void setRetrySleepMillis(int retrySleepMillis) {
    this.retrySleepMillis = retrySleepMillis;
  }

  /**
   * <pre>
   * 设置当微信系统响应系统繁忙时，最大重试次数
   * 默认：5次
   * </pre>
   * @param maxRetryTimes
   */
  public void setMaxRetryTimes(int maxRetryTimes) {
    this.maxRetryTimes = maxRetryTimes;
  }
  
  /**
   * 统一下单(详见http://pay.weixin.qq.com/wiki/doc/api/index.php?chapter=9_1)
   * 在发起微信支付前，需要调用统一下单接口，获取"预支付交易会话标识"
   * @param openId 支付人openId
   * @param outTradeNo 商户端对应订单号
   * @param amt 金额(单位元)
   * @param body 商品描述
   * @param tradeType 交易类型 JSAPI，NATIVE，APP，WAP
   * @param ip 发起支付的客户端IP
   * @param notifyUrl 通知地址
   * @return
   */
  public WxMpPrepayIdResult getPrepayId(String openId, String outTradeNo, BigDecimal amt, String body, String tradeType, String ip, String callbackUrl,String product_id,String attach) {
    String nonce_str = RandomUtils.getRandomStr();

    SortedMap<String, String> packageParams = new TreeMap<String, String>();
    packageParams.put("appid", wxMpConfigStorage.getAppId());
    packageParams.put("mch_id", wxMpConfigStorage.getPartnerId());
    packageParams.put("nonce_str", nonce_str);
    packageParams.put("body", body);
    packageParams.put("out_trade_no", outTradeNo);

    packageParams.put("total_fee", Integer.toString(amt.multiply(new BigDecimal("100")).intValue()));
    packageParams.put("spbill_create_ip", ip);
    packageParams.put("notify_url", callbackUrl);
    packageParams.put("trade_type", tradeType);
    if(openId!=null)
    	packageParams.put("openid", openId);
    if(product_id!=null)
    	packageParams.put("product_id", product_id);
    if(attach!=null)
    	packageParams.put("attach", attach);

    String sign = WxCryptUtil.createSign(packageParams, wxMpConfigStorage.getPartnerKey());
    String xml = "<xml>" +
        "<appid>" + wxMpConfigStorage.getAppId() + "</appid>" +
        "<mch_id>" + wxMpConfigStorage.getPartnerId() + "</mch_id>" +
        "<nonce_str>" + nonce_str + "</nonce_str>" +
        "<sign>" + sign + "</sign>" +
        "<body><![CDATA[" + body + "]]></body>" +
        "<out_trade_no>" + outTradeNo + "</out_trade_no>" +
        "<total_fee>" + packageParams.get("total_fee") + "</total_fee>" +
        "<spbill_create_ip>" + ip + "</spbill_create_ip>" +
        "<notify_url>" + callbackUrl + "</notify_url>" +
        "<trade_type>" + tradeType + "</trade_type>";
    if(openId!=null)
    	xml +="<openid>" + openId + "</openid>";
    if(product_id!=null)
    	xml +="<product_id>" + product_id + "</product_id>";
    if(attach!=null)
    	xml +="<attach>" + attach + "</attach>";
    xml +="</xml>";
    log.debug(xml);
    try {
    	String responseContent = WeixinUtils.httpRequest("https://api.mch.weixin.qq.com/pay/unifiedorder", RequestMethodEnum.POST, xml);
    	log.debug("prepayid:{}",responseContent);
    	WxMpPrepayIdResult wxMpPrepayIdResult = WxMpPrepayIdResult.fromXml(responseContent);
    	return wxMpPrepayIdResult;
    } catch (Exception e) {
    	e.printStackTrace();
    }
    return new WxMpPrepayIdResult();
  }

  /**
   * 该接口调用“统一下单”接口，并拼装JSSDK发起支付请求需要的参数
   * 详见http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html#.E5.8F.91.E8.B5.B7.E4.B8.80.E4.B8.AA.E5.BE.AE.E4.BF.A1.E6.94.AF.E4.BB.98.E8.AF.B7.E6.B1.82
   * @param openId 支付人openId
   * @param outTradeNo 商户端对应订单号
   * @param amt 金额(单位元)
   * @param body 商品描述
   * @param tradeType 交易类型 JSAPI，NATIVE，APP，WAP
   * @param ip 发起支付的客户端IP
   * @param notifyUrl 通知地址
   * @return
   */
  public Map<String, String> getJSSDKPayInfo(String openId, String outTradeNo, BigDecimal amt, String body, String tradeType, String ip, String callbackUrl) {
    WxMpPrepayIdResult wxMpPrepayIdResult = getPrepayId(openId, outTradeNo, amt, body, tradeType, ip, callbackUrl,null,null);
    String prepayId = wxMpPrepayIdResult.getPrepay_id();
    if (prepayId == null || prepayId.equals("")) {
      throw new RuntimeException("error："+wxMpPrepayIdResult.getReturn_msg());
    }

    Map<String, String> payInfo = new HashMap<String, String>();
    payInfo.put("appId", wxMpConfigStorage.getAppId());
    // 支付签名时间戳，注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
    payInfo.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000));
    payInfo.put("nonceStr", RandomUtils.getRandomStr());
    payInfo.put("package", "prepay_id=" + prepayId);
    payInfo.put("signType", "MD5");

    String finalSign = WxCryptUtil.createSign(payInfo, wxMpConfigStorage.getPartnerKey());
    payInfo.put("sign", finalSign);
    return payInfo;
  }
  
  /**
   * 根据商户订单查询支付订单
   * @param outTradeNo
   * @return
   */
  public WxMpPayOrderQuery queryWxPayOrder(String outTradeNo){
	  String nonce_str = RandomUtils.getRandomStr();

	  SortedMap<String, String> packageParams = new TreeMap<String, String>();
	  packageParams.put("appid", wxMpConfigStorage.getAppId());
	  packageParams.put("mch_id", wxMpConfigStorage.getPartnerId());
	  packageParams.put("nonce_str", nonce_str);
	  packageParams.put("out_trade_no", outTradeNo);

	  String sign = WxCryptUtil.createSign(packageParams, wxMpConfigStorage.getPartnerKey());
	  String xml = "<xml>" +
			  "<appid>" + wxMpConfigStorage.getAppId() + "</appid>" +
			  "<mch_id>" + wxMpConfigStorage.getPartnerId() + "</mch_id>" +
			  "<out_trade_no>" + outTradeNo + "</out_trade_no>" +
			  "<nonce_str>" + nonce_str + "</nonce_str>" +
			  "<sign>" + sign + "</sign>" +
			  "</xml>";

	  try {
		  String responseContent = WeixinUtils.httpRequest("https://api.mch.weixin.qq.com/pay/orderquery", RequestMethodEnum.POST, xml);
		  log.debug("payQuery:{}",responseContent);
		  WxMpPayOrderQuery wxMpPayOrderQuery = WxMpPayOrderQuery.fromXml(responseContent);
		  return wxMpPayOrderQuery;
	  } catch (Exception e) {
		  e.printStackTrace();
	  }
	  return new WxMpPayOrderQuery();
  }
}
